import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';

import { message } from '@/components/AntdGlobal';
import { IResponseData } from '@/types/api';
import { hideLoading, showLoading } from '@/utils/loading';
import storage from '@/utils/storage';

import { getStatusMessage } from './statusMessage';

const API_BASEURL = import.meta.env.VITE_APP_API_BASEURL;
const MOCK_API_BASEURL = import.meta.env.VITE_APP_MOCK_API_BASEURL;
const USE_MOCK = import.meta.env.VITE_APP_USE_MOCK;
export const baseURL = USE_MOCK === 'true' ? MOCK_API_BASEURL : API_BASEURL;
/**
 * 1.创建实例
 */
const instance = axios.create({
    // 请求的根路径
    baseURL,
    // 请求超时时间ms
    timeout: 1000 * 60,
    // 超时提示文案
    timeoutErrorMessage: '请求超时，请稍后再试',
    // 支持跨域请求携带cookie
    withCredentials: true,
    // 请求头配置
    headers: {
        'Content-Type': 'application/json;charset=utf-8'
    }
});

/**
 * 2.请求拦截器
 * 	=> 追加token
 */
instance.interceptors.request.use(
    (config) => {
        // 全局loading
        if (config.showLoading) {
            showLoading();
        }
        // TODO:加密请求数据
        // TODO:净化请求参数，防止携带注入攻击脚本

        // 追加token
        const token = storage.get('token');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }

        return { ...config };
    },
    (error: AxiosError) => {
        return Promise.reject(error);
    }
);

/**
 * 3.响应拦截器
 *  => 结束请求
 */
instance.interceptors.response.use(
    (response: AxiosResponse) => {
        // 2xx 范围内的状态码都会触发该函数
        return response;
    },
    (error: AxiosError) => {
        // 超出 2xx 范围的状态码都会触发该函数。
        return Promise.reject(error);
    }
);

/**
 * 4.二次拦截处理
 *
 * @param config 请求配置参数
 * @returns 返回Promise对象，解析值为BaseResponse<T>类型
 * @template T 泛型参数，用于指定返回数据的类型
 */
const requestInstance = <T = any>(extendedConfig: AxiosRequestConfig): Promise<IResponseData<T>> => {
    // 初始化默认扩展配置参数
    const { showMessage = false, showError = true, ...config } = extendedConfig;

    // TODO:重复请求过滤

    return (
        instance
            .request<any, AxiosResponse<IResponseData<T>, any>, any>(config)
            // 二次拦截:处理成功情况
            .then((response: AxiosResponse<IResponseData<T>, any>) => {
                // `data` 由服务器提供的响应
                const res = response.data;
                // 后端code约定，code为000000时表示请求成功处理完成返回数据
                if (res.code !== '000000') {
                    // 抛出错误，定义错误信息
                    const error = new Error(res.msg);
                    // 传递给二次拦截:处理失败情况
                    return Promise.reject(error);
                }
                // 是否全局弹框显示成功消息
                if (showMessage) {
                    message.success(res.msg);
                }
                // 返回数据
                return res;
            })
            // 二次拦截:处理失败情况
            .catch((error: AxiosError & Error) => {
                // 此处使用TS类型的类型兼容，兼容axios的错误类型和普通的Error类型
                const { response, request } = error;
                if (response) {
                    // 请求成功发出且服务器也响应了状态码，但状态代码超出了 2xx 的范围
                    showError && message.error(getStatusMessage(response.status));
                } else if (request) {
                    // 请求已经成功发起，但没有收到响应
                    showError && message.error('网络请求失败，请稍后再试');
                } else {
                    // 其他发送请求问题
                    showError && message.error(error.message || '网络请求失败，请稍后再试');
                }
                // 抛出错误，让调用者捕获到这个错误,以便 (try...catch...结构捕获到这个错误，并进行后续的错误处理)
                return Promise.reject(error);
            })
            // 二次拦截:总是会执行
            .finally(() => {
                // 隐藏全局loading
                hideLoading();
            })
    );
};

export default {
    get<T = any>(
        url: string,
        params = {},
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ): Promise<IResponseData<T>> {
        return requestInstance({ url, method: 'GET', params, ...config });
    },
    post<T = any>(
        url: string,
        data = {},
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ): Promise<IResponseData<T>> {
        return requestInstance({ url, method: 'POST', data, ...config });
    },
    put<T = any>(
        url: string,
        data = {},
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ): Promise<IResponseData<T>> {
        return requestInstance({ url, method: 'PUT', data, ...config });
    },
    patch<T = any>(
        url: string,
        data = {},
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ): Promise<IResponseData<T>> {
        return requestInstance({ url, method: 'PATCH', data, ...config });
    },
    del<T = any>(
        url: string,
        params = {},
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ): Promise<IResponseData<T>> {
        return requestInstance({ url, method: 'DELETE', params, ...config });
    },
    downloadFile(
        url: string,
        data = {},
        fileName: string = 'filename.xlsx',
        // 请求配置，内部类型已经扩展
        config: AxiosRequestConfig = {}
    ) {
        // 初始化默认扩展配置参数
        const { showMessage = false, showError = true } = config;
        return (
            instance
                .request<any, AxiosResponse<any, any>, any>({
                    url,
                    method: 'POST',
                    data,
                    ...config,
                    responseType: 'blob'
                })
                // 二次拦截:处理成功情况
                .then((response: AxiosResponse<any, any>) => {
                    // 实例化Blob实例
                    const blob = new Blob([response.data], {
                        type: response.data.type
                    });
                    // 约定文件名放在header中
                    const name = (response.headers['file-name'] as string) || fileName;
                    const link = document.createElement('a');
                    // 保存文件名称，解构防止乱码
                    link.download = decodeURIComponent(name);
                    // 构造下载链接(file or blob 对象创建为url 也可用于本地上传图片预览)
                    link.href = URL.createObjectURL(blob);
                    document.body.append(link);
                    link.click();
                    // 移除
                    document.body.removeChild(link);
                    // 关键：释放文件对象，释放内存
                    window.URL.revokeObjectURL(link.href);

                    // 是否全局弹框显示成功消息
                    if (showMessage) {
                        message.success('下载成功');
                    }
                })
                // 二次拦截:处理失败情况
                .catch((error: AxiosError & Error) => {
                    // 此处使用TS类型的类型兼容，兼容axios的错误类型和普通的Error类型
                    const { response, request } = error;
                    if (response) {
                        // 请求成功发出且服务器也响应了状态码，但状态代码超出了 2xx 的范围
                        showError && message.error(getStatusMessage(response.status));
                    } else if (request) {
                        // 请求已经成功发起，但没有收到响应
                        showError && message.error('网络请求失败，请稍后再试');
                    } else {
                        // 其他发送请求问题
                        showError && message.error(error.message || '网络请求失败，请稍后再试');
                    }
                    // 抛出错误，让调用者捕获到这个错误,以便 (try...catch...结构捕获到这个错误，并进行后续的错误处理)
                    return Promise.reject(error);
                })
                // 二次拦截:总是会执行
                .finally(() => {
                    // 隐藏全局loading
                    hideLoading();
                })
        );
    }
};
